package net.cassite.pure.ioc;

import net.cassite.pure.ioc.annotations.Invoke;
import net.cassite.pure.ioc.annotations.Wire;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

import static net.cassite.style.reflect.Reflect.*;

/**
 * Base of pojo classes requiring auto wire.<br>
 * Gives POJO (or a class with setters) the capability of autowiring. <br>
 * Simply <b>new</b> a class, then all setters would be automatically called
 * <br>
 * with corresponding parameters.
 *
 * @author wkgcass
 */
public abstract class AutoWire {

        private static final Logger LOGGER = LoggerFactory.getLogger(AutoWire.class);

        protected AutoWire() {
                LOGGER.debug("Constructing object {}", this);

                wire(this);

                LOGGER.debug("Finished Constructing {}", this);
        }

        /**
         * Retrieve an instance of given class
         *
         * @param scope ioc scope
         * @param cls     instance of which to retrieve
         * @param <T>     type of the instance
         * @return retreived class
         */
        @SuppressWarnings("unchecked")
        public static <T> T get(Scope scope, Class<T> cls) {
                if (scope == null) {
                        LOGGER.debug("constructing a scope");
                        scope = new Scope(IOCController.rootScope);
                }
                return (T) IOCController.get(scope, cls);
        }

        /**
         * Retrieve an instance of given class
         *
         * @param cls instance of which to retrieve
         * @param <T> type of the instance
         * @return retrieved class
         */
        public static <T> T get(Class<T> cls) {
                return get(null, cls);
        }

        /**
         * Invoke setters and methods with Invoke annotation on given object
         *
         * @param scope ioc scope
         * @param o       the object to wire
         */
        public static void wire(Scope scope, Object o) {
                if (!IOCController.isWithHandlers()) {
                        synchronized (IOCController.class) {
                                if (!IOCController.isWithHandlers()) {
                                        IOCController.autoRegister();
                                        IOCController.closeRegistering();
                                }
                        }
                }
                if (null == scope) {
                        LOGGER.debug("constructing a scope for object {}", o);
                        scope = new Scope(IOCController.rootScope);
                }
                final Scope s = scope;
                if (!o.getClass().getName().contains("$$EnhancerByCGLIB$$")) {
                        // prevent wiring cglib generated objects
                        LOGGER.debug("Start Wiring object {}", o);
                        IOCController.registerSingleton(o);
                        // check field
                        LOGGER.debug("--checking fields");
                        List<String> setterNamesOfWiredFields = new ArrayList<>();
                        cls(o).allFields().stream().filter(f -> f.isAnnotationPresent(Wire.class)).forEach(f -> {
                                IOCController.fillField(s, o, f);
                                setterNamesOfWiredFields.add("set" + f.name().substring(0, 1).toUpperCase() + f.name().substring(1));
                        });
                        LOGGER.debug("--field wiring completed");
                        LOGGER.debug("--checking setters");
                        cls(o).setters().stream().filter(m -> !setterNamesOfWiredFields.contains(m.name())).forEach(m -> IOCController.invokeSetter(s, o, m));
                        LOGGER.debug("--setter wiring completed");
                        LOGGER.debug("Finished Wiring {}", o);

                        LOGGER.debug("Start Invoking methods of object {}", o);
                        cls(o).allMethods().forEach(m -> {
                                if (m.annotation(Invoke.class) != null && !m.isStatic()) {
                                        IOCController.invokeMethod(s, m, o);
                                }
                        });
                        LOGGER.debug("Finished Invoking methods of object {}", o);
                }
        }

        /**
         * Invoke setters and methods with Invoke annotation on given object
         *
         * @param o the object to wire
         */
        public static void wire(Object o) {
                wire(null, o);
        }
}
